"""
Open Source Dobot GUI Application: open-dobot-gui
Contains main function for entire application, GUI initilization and functions
First Author: Mike Ferguson www.mikeahferguson.com 3/26/2016
Additional Authors (Add your name below):
1.
License: MIT

Requires PyQt5 to be installed.

Anything Qt specific (functions, classes, etc.. starts with the letter Q)

The GUI template is created by a program called QtDesigner, which spits out a .ui file, which is basically just an XML
file that describes the GUI elements. In the designer, the elements are given object names. The first step of the code
below is to load the .ui file and get a reference to it.
"""

import serial, time, struct
import sys, os, threading, platform
from threading import Thread

from PyQt5.QtCore import QTimer, pyqtSlot, pyqtSignal, QRunnable, QThreadPool, QFile, QIODevice, QByteArray, \
    QJsonDocument, QStringListModel, QSize, QDir
from PyQt5.QtGui import QStandardItemModel, QStandardItem
from PyQt5.QtWidgets import QApplication, QMainWindow, QListWidget, QListWidgetItem, QFileDialog, QMessageBox, QLabel, \
    QPushButton, QSizePolicy
from PyQt5 import uic, QtCore, QtGui, sip
import DobotInverseKinematics
import serial.tools.list_ports
import demjson

from Actions import Action
from CustomWidget import CustomWidget
from collections import deque

def encode(s):
    return s.encode('utf-8')


# This loads the GUI from the .ui file that is created by QtDesigner. The .ui file should be in the same folder as this
# python file (or specify different path).
Ui_MainWindow, QtBaseClass = uic.loadUiType('DobotMainUi.ui')


# Here, a class is defined to represent the entire GUI. It is derived from a Qt class named QMainWindow, which
# corresponds to the GUI type specified in QtDesigner. All of the functional aspects (as opposed to design aspects) of
# the GUI are defined in this class. For example, what happens when a user presses a button.
class DobotGUIApp(QMainWindow):
    # class initialization function (initialize the GUI)
    def __init__(self, parent=None):
        # I'm a python noob, but I'm guessing this means initialize the parent class. I imagine all the super classes
        # have to be explicitly initialized.
        super(DobotGUIApp, self).__init__(parent)
        # This sets up the ui variable of this class to refer to the loaded .ui file.
        self.ui = Ui_MainWindow()
        # This call is required. Does whatever set up is required, probably gets references to the elements and so on.
        self.ui.setupUi(self)

        if platform.system() is "raspi":
            self.showFullScreen()
            screen_geo = QApplication.desktop().screenGeometry()
            self.setFixedSize(QSize(screen_geo.width(), screen_geo.height()))


        self.arduinoSerial = None

        # angle mode or coor mode
        self.mode = 94

        self.current_pos = {}

        self.selected_folder = ""

        self.tasks_queue = deque([])
        self.exe_files_list = [] #需要执行指令的文件名列表
        self.current_exe_index = 0 #当前正在执行的
        self._running_flag = True
        self._fan_state = False

        self.load_cmds_buttons()

        QThreadPool.globalInstance().setMaxThreadCount(1)

        # Anything named after self.ui. (e.g. self.ui.x) means you are referring to an object name x that corresponds
        # to an element in the gui. Qt uses a signals and slots framework to define what happens in response to UI
        # events. You'll have to look that up if you're interested in it.

        ###
        # Connect gui elements in the .ui file to event handling functions.
        ###

        self.ui.pushButton_connect.clicked.connect(self.pushButton_connect_clicked)
        self.ui.pushButton_connect_user.clicked.connect(self.pushButton_connect_user_clicked)

        # connect serial ports list refresh button clicked event to the update serial port list function
        self.ui.pushButtonRefreshSerialPortsList.clicked.connect(self.update_serial_port_list)
        # connect move coordinates button clicked event to function to move to the coordinate specified
        self.ui.pushButtonMoveToCoordinate.clicked.connect(self.pushButtonMoveToCoordinate_clicked)
        # connect move to angles button clicked event to function to move to the angles specified
        self.ui.pushButtonMoveToAngles.clicked.connect(self.pushButtonMoveToAngles_clicked)

        # send home cmd
        self.ui.pushButton_home.clicked.connect(self.pushButton_home_clicked)
        self.ui.pushButton_home_user.clicked.connect(self.pushButton_home_clicked)

        self.ui.pushButton_x_add.clicked.connect(self.pushButton_x_add_clicked)
        self.ui.pushButton_x_sub.clicked.connect(self.pushButton_x_sub_clicked)

        self.ui.pushButton_y_add.clicked.connect(self.pushButton_y_add_clicked)
        self.ui.pushButton_y_sub.clicked.connect(self.pushButton_y_sub_clicked)

        self.ui.pushButton_z_add.clicked.connect(self.pushButton_z_add_clicked)
        self.ui.pushButton_z_sub.clicked.connect(self.pushButton_z_sub_clicked)

        self.ui.pushButton_record.clicked.connect(self.pushButton_record_clicked)
        ###
        # Define application class variables.
        ###

        self.ui.listWidget.itemDoubleClicked.connect(self.listWidget_itemDoubleClicked)
        self.ui.listWidget.itemClicked.connect(lambda item: self.ui.listWidget.openPersistentEditor(item))

        self.ui.listWidget_files.itemClicked.connect(self.listWidget_files_itemClicked)
        self.ui.listWidget_files.itemDoubleClicked.connect(self.listWidget_files_itemDoubleClicked)

        self.ui.pushButton_del.clicked.connect(self.pushButton_del_clicked)

        self.ui.pushButton_save.clicked.connect(self.pushButton_save_clicked)
        self.ui.pushButton_load.clicked.connect(self.pushButton_load_clicked)
        self.ui.pushButton_run.clicked.connect(self.pushButton_run_clicked)

        self.ui.pushButton_wait_add.clicked.connect(self.pushButton_wait_add_clicked)
        self.ui.pushButton_io_add.clicked.connect(self.pushButton_io_add_clicked)

        self.ui.pushButton_cmd.clicked.connect(self.pushButton_cmd_clicked)

        self.ui.pushButton_start.clicked.connect(lambda: (self.set_running_state(True), self.exec_cmd()))
        self.ui.pushButton_stop.clicked.connect(lambda: self.set_running_state(False))
        self.ui.pushButton_cancel.clicked.connect(self.pushButton_cancel_clicked)

        self.ui.pushButton_left.clicked.connect(self.pushButton_cancel_clicked)
        self.ui.pushButton_mid.clicked.connect(self.pushButton_mid_clicked)
        self.ui.pushButton_right.clicked.connect(self.pushButton_left_clicked)
        self.ui.pushButton_fan.clicked.connect(self.pushButton_fan_clicked)

        ###
        # General initialization code
        ###Tt

        # populate the serial ports list widget
        self.update_serial_port_list()

        self.timer = QTimer(self)
        self.timer.setInterval(2000)
        self.timer.timeout.connect(self.get_coordinate)

    def load_cmds_buttons(self):
        folder = QDir.currentPath()
        folder = "{}/actionstest".format(folder)
        self.selected_folder = folder

        self.clear_layout(self.ui.gridLayout_cmds_buttons)

        row = 4
        # col = 0
        for i, path in enumerate(os.listdir(folder)):
            # print(i, path)
            button = QPushButton(path)
            button.clicked.connect(self.button_cmd_clicked)
            sp = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
            button.setSizePolicy(sp)
            self.ui.gridLayout_cmds_buttons.addWidget(button, i / row, i % row)

    def clear_layout(self, layout):
        count = layout.count()
        # print(count)
        while count > 0:
            item = layout.takeAt(0)
            widget = item.widget()
            layout.removeWidget(widget)
            sip.delete(widget)
            count = count - 1

        layout.update()

    def button_cmd_clicked(self):
        widget = self.sender()
        self.exe_files_list.append(widget.text())
        slm = QStringListModel()
        slm.setStringList(self.exe_files_list)
        self.ui.listView_exe_files.setModel(slm)
        # self.
        self.set_running_state(True)
        self.pushButton_run_clicked()


    def is_running(self):
        return self._running_flag

    def set_running_state(self, state):
        self._running_flag = state

    def pushButton_connect_clicked(self):
        try:
            # variable for arduino serial. I'm connecting to the serial named COM8 here (I'm using windows 10)
            # the second argument is the baud rate, or how fast the serial connection is. needs to be the same in firmware
            # This will reset the Arduino, and make the LED flash once.
            items = self.ui.listWidgetSerialPorts.selectedItems()
            if not items:
                self.show_a_warning_message_box(
                    '没有选择端口',
                    "0",
                    '请选择端口')
                return

            baud = self.ui.lineEdit_baud.text()
            self.arduinoSerial = serial.Serial(items[0].text(), int(baud), timeout=10)
            # Must give Arduino time to reset
            #  "Any time less than this does not seem to work..." quoted from someone else on a blog
            time.sleep(1.5)
            # I think this clears the input stream?
            # I think I may not need this, but I'm including it anyways, against the advice of someone on stackexchange.
            # I don't think it does anything bad, just one more line of unnecessary code.
            # self.arduinoSerial.timeout
            self.arduinoSerial.flushInput()
        except Exception as e:
            self.show_a_warning_message_box(
                'Unknown error connecting to the arduino serial port. Code error shown below:',
                repr(e),
                'Arduino Serial Connection Error')
        else:
            self.ui.label_con_status.setText("Connected")
            self.ui.label_con_port.setText(str(self.arduinoSerial.port))
            self.ui.label_con_baud.setText(str(self.arduinoSerial.baudrate))
            # self.timer.start()

    def pushButton_connect_user_clicked(self):
        try:
            listOfSerialPorts = serial.tools.list_ports.comports()
            for portName in listOfSerialPorts:
                port = portName[0]
                break

            baud = self.ui.lineEdit_baud.text()
            self.arduinoSerial = serial.Serial(port, int(baud), timeout=10)
            # Must give Arduino time to reset
            #  "Any time less than this does not seem to work..." quoted from someone else on a blog
            time.sleep(1.5)
            # I think this clears the input stream?
            # I think I may not need this, but I'm including it anyways, against the advice of someone on stackexchange.
            # I don't think it does anything bad, just one more line of unnecessary code.
            # self.arduinoSerial.timeout
            self.arduinoSerial.flushInput()
        except Exception as e:
            self.show_a_warning_message_box(
                'Unknown error connecting to the arduino serial port. Code error shown below:',
                repr(e),
                'Arduino Serial Connection Error')
        else:
            self.ui.label_con_status.setText("Connected")
            self.ui.label_con_port.setText(str(self.arduinoSerial.port))
            self.ui.label_con_baud.setText(str(self.arduinoSerial.baudrate))
            self.ui.pushButton_connect_user.setText("连接成功")


    def clear_list_widget(self, listWidget):
        count = listWidget.count()
        for i in range(count):
            # item = self.ui.listWidget.item(i)
            item = listWidget.takeItem(0)
            if item:
                # 删除widget
                listWidget.removeItemWidget(item)
                del item

    def listWidget_itemDoubleClicked(self, item: QListWidgetItem):
        self.set_absolute_mode()
        self.arduinoSerial.write(encode(item.text()))

    def listWidget_files_itemClicked(self, item: QListWidgetItem):
        # 先清除原来的item
        self.clear_list_widget(self.ui.listWidget)

        path = os.path.join(self.selected_folder, item.text())
        with open(file=path, mode='r', encoding='utf-8') as file:
            json_array_str = file.read()
            json_obj = QJsonDocument.fromJson(QByteArray(str(json_array_str).encode("utf-8"))).object()
            array_obj = json_obj["array"]
            array = array_obj.toArray()

            for j in array:
                self.ui.listWidget.addItem(QListWidgetItem(j.toObject()["cmd"].toString()))

    def listWidget_files_itemDoubleClicked(self, item: QListWidgetItem):
        self.exe_files_list.append(item.text())
        self.ui.label_current_file.setText("({})".format(len(self.exe_files_list)))
        slm = QStringListModel()
        slm.setStringList(self.exe_files_list)
        self.ui.listView_exe_files.setModel(slm)
        # self.
        # self.set_running_state(True)
        # self.pushButton_run_clicked()

    def pushButton_del_clicked(self):
        item_lists = self.ui.listWidget.selectedItems()
        for item in item_lists:
            # self.ui.listWidget.removeItemWidget(i)
            row = self.ui.listWidget.indexFromItem(item).row()
            # print(row)
            # 删除item
            item = self.ui.listWidget.takeItem(row)
            # print(item)
            # 删除widget
            self.ui.listWidget.removeItemWidget(item)
            del item

    def pushButton_save_clicked(self):
        path, _ = QFileDialog.getSaveFileName(self, "请选择文件", "", "Text files (*.txt)")
        if not path:
            return

        count = self.ui.listWidget.count()
        print(count)
        if not count > 0:
            return

        # json_list = []
        data = QByteArray()
        json = QJsonDocument.fromJson(data).object()  # 创建空的QJsonObject对象
        jsonarry = QJsonDocument.fromJson(data).array()  # 创建空的QJsonArray对象。
        for i in range(count):
            item = self.ui.listWidget.item(i)
            s = item.text()

            json_pos = QJsonDocument.fromJson(data).object()
            json_pos["cmd"] = s
            jsonarry.append(json_pos)

        json["array"] = jsonarry
        output_json = QJsonDocument(json).toJson(QJsonDocument.Indented)
        with open(file=path, mode='w+', encoding='utf-8') as file:
            # file.write(text)
            file.write(output_json.data().decode("utf-8"))

    def pushButton_load_clicked(self):
        self.clear_list_widget(self.ui.listWidget_files)

        folder = QFileDialog.getExistingDirectory(self, "请选择目录", "", QFileDialog.ShowDirsOnly)
        if not folder:
            return

        self.selected_folder = folder

        for path in os.listdir(folder):
            self.ui.listWidget_files.addItem(QListWidgetItem(path))

    def pushButton_run_clicked(self):
        if len(self.exe_files_list) == 0:
            return

        self.set_absolute_mode()
        self.exec_cmd()
        self.timer.stop()

    def pushButton_wait_add_clicked(self):
        pin = self.ui.lineEdit_wait_pin.text()
        state = self.ui.lineEdit_wait_pin_state.text()
        self.ui.listWidget.addItem(QListWidgetItem("M226 P{} S{}\r".format(pin, state)))

    def pushButton_io_add_clicked(self):
        pin = self.ui.lineEdit_io_pin.text()
        state = self.ui.lineEdit_io_state.text()

        if int(state) > 0:
            state = 255

        self.ui.listWidget.addItem(QListWidgetItem("M42 P{} S{}\r".format(pin, state)))

    def pushButton_cmd_clicked(self):
        self.set_absolute_mode()
        cmd = self.ui.lineEdit_cmd.text()
        self.arduinoSerial.write(encode(cmd))

    def pushButton_cancel_clicked(self):
        self.set_running_state(False)
        self.tasks_queue.clear()
        slm = QStringListModel()
        slm.setStringList([])
        self.ui.listView.setModel(slm)

        self.exe_files_list.clear()
        slm = QStringListModel()
        slm.setStringList(self.exe_files_list)
        self.ui.listView_exe_files.setModel(slm)

    def pushButton_left_clicked(self):
        pass

    def pushButton_mid_clicked(self):
        # self.tasks_queue = self.load_cmds_from_file("/home/pi/open-dobot-gui/actionstest/mid_action.txt")
        input_files = [
            '取杯子',
            # '放中45.txt',
            # '等待',
            # '拿中45',
            '放到盖盖子处',
            '放盖子舵机测试.txt',
            '下降.txt',
            '等待',
            '上升.txt',
        ]
        for file in input_files:
            print(file)
            self.exe_files_list.append(file)
        slm = QStringListModel()
        slm.setStringList(self.exe_files_list)
        self.ui.listView_exe_files.setModel(slm)

        self.set_running_state(True)
        self.exec_cmd()
        pass

    def pushButton_fan_clicked(self):
        if self._fan_state:
            cmd = "M106S160\r"
        else:
            cmd = "M107\r"
        self.arduinoSerial.write(encode(cmd))
        self._fan_state = not self._fan_state

    def pushButton_record_clicked(self):
        print(self.current_pos)

        x = self.current_pos["X"].toDouble()
        y = self.current_pos["Y"].toDouble()
        z = self.current_pos["Z"].toDouble()

        self.ui.listWidget.addItem(QListWidgetItem('G1X{}Y{}Z{}\r'.format(x, y, z)))

    def get_coordinate(self):
        # print(1)
        # print(self.arduinoSerial)
        if not self.arduinoSerial:
            return
        action = Action(self.arduinoSerial, encode('G96\r'))
        # print("get_coordinate2")
        action.signals.result.connect(self.show_coordinate)
        action.signals.error.connect(self.show_coordinate)
        QThreadPool.globalInstance().start(action)

    def show_coordinate(self, xyz_str):
        try:
            json = QJsonDocument.fromJson(QByteArray(xyz_str))
            self.current_pos = json
            self.ui.labelCurrentXValue.setText(str(json["X"].toDouble()))
            self.ui.labelCurrentYValue.setText(str(json["Y"].toDouble()))
            self.ui.labelCurrentZValue.setText(str(json["Z"].toDouble()))
        except Exception as e:
            print(e)

    def load_cmds_from_file(self, file_name):
        path = os.path.join(self.selected_folder, file_name)
        cmds = deque([])
        with open(file=path, mode='r', encoding='utf-8') as file:
            json_array_str = file.read()
            json_obj = QJsonDocument.fromJson(QByteArray(str(json_array_str).encode("utf-8"))).object()
            array_obj = json_obj["array"]
            array = array_obj.toArray()

            for j in array:
                cmds.append(j.toObject()["cmd"].toString())

            return cmds

    def exec_cmd(self):
        if not self.is_running():
            return

        if len(self.tasks_queue) == 0:
            if len(self.exe_files_list) == 0:
                # self.timer.start()
                return
            else:
                current_exe_file = self.exe_files_list.pop(0)
                print(current_exe_file)
                self.tasks_queue = self.load_cmds_from_file(current_exe_file)
                print(self.tasks_queue)
                self.ui.label_current_file.setText("({}){}".format(len(self.exe_files_list), current_exe_file))
                slm = QStringListModel()
                slm.setStringList(self.exe_files_list)
                self.ui.listView_exe_files.setModel(slm)

        if len(self.tasks_queue) > 0:
            cmd = self.tasks_queue.popleft()
            self.ui.label_current_cmd.setText(cmd)
            if cmd.find("\r") < 0:
                cmd = cmd + "\r"
            # print("cmdddd = {}".format(cmd.encode("utf-8")))

            action = Action(self.arduinoSerial, encode(cmd))
            action.signals.result.connect(self.exec_cmd)
            QThreadPool.globalInstance().start(action)

            qlist = []
            slm = QStringListModel()
            for cmd in self.tasks_queue:
                # print(type(str(cmd)))
                qlist.append(cmd)
                # qlist.append(cmd.decode("utf-8"))
            # print(qlist)
            slm.setStringList(qlist)
            self.ui.listView.setModel(slm)


    def send_mode(self, mode):
        if self.mode == mode:
            return

        self.mode = mode
        self.arduinoSerial.write(encode('G{}\r'.format(mode)))

    def set_relative_mode(self):
        if self.arduinoSerial:
            self.send_mode(94)
            self.arduinoSerial.write(encode('G{}\r'.format(91)))

    def set_absolute_mode(self):
        if self.arduinoSerial:
            self.send_mode(94)
            self.arduinoSerial.write(encode('G{}\r'.format(90)))

    def pushButton_x_add_clicked(self):
        self.set_relative_mode()
        self.arduinoSerial.write(encode('G1X{}\r'.format(self.ui.lineEdit_x.text())))

    def pushButton_x_sub_clicked(self):
        self.set_relative_mode()
        self.arduinoSerial.write(encode('G1X-{}\r'.format(self.ui.lineEdit_x.text())))

    def pushButton_y_add_clicked(self):
        self.set_relative_mode()
        self.arduinoSerial.write(encode('G1Y{}\r'.format(self.ui.lineEdit_y.text())))

    def pushButton_y_sub_clicked(self):
        self.set_relative_mode()
        # print(encode('G1Y-{}\r'.format(self.ui.lineEdit_y.text())))
        self.arduinoSerial.write(encode('G1Y-{}\r'.format(self.ui.lineEdit_y.text())))

    def pushButton_z_add_clicked(self):
        self.set_relative_mode()
        self.arduinoSerial.write(encode('G1Z{}\r'.format(self.ui.lineEdit_z.text())))

        # self.get_coordinate()

    def pushButton_z_sub_clicked(self):
        self.set_relative_mode()
        self.arduinoSerial.write(encode('G1Z-{}\r'.format(self.ui.lineEdit_z.text())))

        self.get_coordinate()

    def pushButtonMoveToCoordinate_clicked(self):

        # get moveTo coordinate text values from lineedits
        moveToX = self.ui.lineEditMoveToX.text()
        moveToY = self.ui.lineEditMoveToY.text()
        moveToZ = self.ui.lineEditMoveToZ.text()

        # check that the values were not empty
        if (moveToX == '' or moveToY == '' or moveToZ == ''):
            self.show_a_warning_message_box('Missing a coordinate value.',
                                            'Check that you entered a value for each dimension.',
                                            'Invalid coordinate for move to command')
            return

        # convert values from string to float and ensure that the values entered were actually numbers
        try:
            moveToXFloat = float(moveToX)
            moveToYFloat = float(moveToY)
            moveToZFloat = float(moveToZ)
        except Exception as e:
            self.show_a_warning_message_box('Check that your coordinate values are numbers and not letters. The code '
                                            + 'error is shown below:',
                                            repr(e),
                                            'Coordinate value conversion to float error')
            return

        self.set_absolute_mode()
        self.arduinoSerial.write(encode('G1X{}Y{}Z{}\r'.format(moveToXFloat, moveToYFloat, moveToZFloat)))

    def pushButtonMoveToAngles_clicked(self):
        # get moveTo angle text values from lineedits
        moveToBaseAngle = self.ui.lineEditMoveToBaseAngle.text()
        moveToUpperArmAngle = self.ui.lineEditMoveToUpperArmAngle.text()
        moveToLowerArmAngle = self.ui.lineEditMoveToLowerArmAngle.text()

        # check that the values were not empty
        if (moveToBaseAngle == '' or moveToUpperArmAngle == '' or moveToLowerArmAngle == ''):
            self.show_a_warning_message_box('Missing a angle value.',
                                            'Check that you entered a value for each angle.',
                                            'Invalid angles for move to command')
            return

        # convert values from string to float and ensure that the values entered were actually numbers
        try:
            moveToBaseAngleFloat = float(moveToBaseAngle)
            moveToUpperArmAngleFloat = float(moveToUpperArmAngle)
            moveToLowerArmAngleFloat = float(moveToLowerArmAngle)
        except Exception as e:
            self.show_a_warning_message_box('Check that your angle values are numbers and not letters. The code '
                                            + 'error is shown below:',
                                            repr(e),
                                            'Angle value conversion to float error')
            return

        self.send_mode(95)
        self.arduinoSerial.write(
            encode('G1X{}Y{}Z{}\r'.format(moveToLowerArmAngleFloat, moveToUpperArmAngleFloat, moveToBaseAngleFloat)))

    def pushButton_home_clicked(self):
        cmds = ['G28\r', 'G95\r', 'G1X-20Y-70\r', 'G94\r']

        for c in cmds:
            self.tasks_queue.append(c)

        self.set_running_state(True)
        self.exec_cmd()
        # self.arduinoSerial.write(encode('G28\r'))
        # self.send_mode(95)
        # self.arduinoSerial.write(encode('G1X-20Y-70\r'))

    # angles passed as arguments here should be real world angles (horizontal = 0, below is negative, above is positive)
    # i.e. they should be set up the same way as the unit circle is
    def check_for_angle_limits_is_valid(self, baseAngle, upperArmAngle, lowerArmAngle):

        returnBool = True
        # implementing limit switches and IMUs will make this function more accurate and allow the user to calibrate the limits
        # necessary for this function.
        # Not currently checking the base angle

        # check the upperArmAngle
        # max empirically determined to be around 107 - 108 degrees. Using 105.
        # min empirically determined to be around -23/24 degrees. Using -20.
        if (-20 <= upperArmAngle <= 105):
            # do nothing, return value already initialized true
            pass
        else:
            self.show_a_warning_message_box('Upper arm angle out of range.',
                                            'Upper arm must have an angle between -20 and 105 degrees. It is mechanically constrained.',
                                            'Upper Arm Range Error')
            returnBool = False

        # check the lowerArmAngle
        # the valid Lower Arm angle is dependent on the upper arm angle. The real world angle of the lower arm (0 degrees = horizontal) needs to be evaluated.
        # min empirically determined to be around -105 degrees. Using -102.
        # max empirically determined to be around 21 degrees. Using 18.

        if (-102 <= lowerArmAngle <= 18):
            # do nothing, already initialized true
            pass
        else:
            self.show_a_warning_message_box('Lower arm angle out of range.',
                                            'Lower arm must have a real world angle between -102 and 18 degrees. It is mechanically constrained.',
                                            'Lower Arm Range Error')
            returnBool = False

        return returnBool

    def update_serial_port_list(self):
        """
        Updates the serial ports qtablewidget in the configuration tab with qlistwidgetitems containing text values
        that are the names of the serial port. Calls function to get a list of ports. This function works for
        Windows, Mac, and Linux
        """
        # must clear the listwidget of any previous items
        self.ui.listWidgetSerialPorts.clear()
        # get a list of all serial ports (all, not just open ones)
        listOfSerialPorts = serial.tools.list_ports.comports()
        # add each serial port name (string) to the list as a qlistwidgetitem with the string value
        for portName in listOfSerialPorts:
            item = QListWidgetItem(portName[0])
            self.ui.listWidgetSerialPorts.addItem(item)

    def show_a_warning_message_box(self, text, infoText, windowTitle):
        msg = QMessageBox()
        msg.setIcon(QMessageBox.Warning)
        msg.setText(text)
        msg.setInformativeText(infoText)
        msg.setWindowTitle(windowTitle)
        msg.exec()


# main function
if __name__ == '__main__':
    # These first three lines initialize the Qt application/GUI.
    app = QApplication(sys.argv)
    window = DobotGUIApp()
    # displays the GUI
    window.show()

    # write whatever set up or logic code you want to here.

    # Says to exit the whole code when the Qt application is closed. app.exec returns some value when qt app quits
    sys.exit(app.exec_())
